package httpd

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"os"
	"path"
	"strconv"
	"strings"

	"gitee.com/asphodelus_dev/go-s3-local/fs"
	"github.com/gofiber/fiber/v2"

	"gitee.com/asphodelus_dev/go-s3-local/util"
)

const basicPathSuffix = "buckets"

// done
func (s *App) createBucket(c *fiber.Ctx) error {
	bucketName := c.Params("bucket")
	_, err := s.svc.CreateBucket(bucketName)
	if err != nil {
		return c.Status(http.StatusInternalServerError).XML(ErrorResponse{err.Error()})
	}
	// bucket := Bucket{}
	// bucket.Name = bucketName
	// bucket.CreationDate = util.DateFormatToSecond(time.Now())

	return c.Status(http.StatusOK).XML(nil)
}

func (s *App) uploadFileOrUploadChunk(c *fiber.Ctx) error {
	uploadId := c.Query("uploadId")
	partNumber := c.Query("partNumber")
	log.Printf("uploadId: %s, partNumber: %s\n", uploadId, partNumber)
	if uploadId != "" && partNumber != "" {
		return s.uploadChunk(c)
	} else {
		return s.uploadFile(c)
	}
}

// done
func (s *App) uploadChunk(c *fiber.Ctx) error {
	bucket := c.Params("bucket")
	object := c.Params("object")
	objectSuffix := c.Params("*")
	uploadId := c.Query("uploadId")
	objectKey := path.Join(object, objectSuffix)
	partNumber := c.Query("partNumber")
	data := c.BodyRaw()
	etag, err := s.svc.UploadPart(bucket, objectKey, partNumber, uploadId, data)
	if err != nil {
		return c.Status(http.StatusInternalServerError).XML(ErrorResponse{err.Error()})
	}
	c.Set("ETag", util.BytesToString(util.StringToBytes(etag)))
	return c.Status(http.StatusOK).XML(nil)
}

// uploadFile 直接上传文件
// done
func (s *App) uploadFile(c *fiber.Ctx) error {
	bucket := c.Params("bucket")
	object := c.Params("object")
	objectSuffix := c.Params("*")
	copySource := c.GetRespHeader("x-amz-copy-source")
	objectKey := path.Join(object, objectSuffix)
	log.Println("进入uploadFile接口")
	if copySource == "" {
		log.Printf("开始文件上传， object路径：%s\n", objectKey)
		data := c.BodyRaw()
		err := s.svc.PutObject(bucket, objectKey, data)
		if err != nil {
			fmt.Printf("Error uploading : %s", err)
			return c.Status(http.StatusBadRequest).XML(err.Error())
		}
		return c.Status(http.StatusOK).XML(nil)
	} else {
		if strings.Contains(copySource, "\\?") {
			copySource = strings.Split(copySource, "\\?")[0]
		}

		copyList := strings.Split(copySource, "\\/")
		copyList = copyList[:len(copyList)-1]
		sourceBucketName := ""
		for _, it := range copyList {
			if it != "" {
				sourceBucketName = it
				break
			}
		}
		var res bytes.Buffer
		for i := 1; i < len(copyList); i++ {
			res.WriteString(copyList[i])
			res.WriteString("/")
		}
		sourceObjectKey := res.String()
		err := s.copyObject(sourceBucketName, sourceObjectKey, bucket, objectKey)
		if err != nil {
			return c.Status(http.StatusInternalServerError).XML(ErrorResponse{err.Error()})
		}
		return c.Status(http.StatusOK).XML(UploadFileResp{
			ETag:         util.EncryptByMD5(copySource),
			LastModified: util.UTCDateFormat(),
		})
	}
}

func (s *App) copyObject(srcBucket, srcObjectKey, bucket, objectKey string) error {
	return s.svc.CopyObject(srcBucket, srcObjectKey, bucket, objectKey)
}

func (s *App) initChunkOrCombineChunk(c *fiber.Ctx) error {
	// uploads := c.Params("uploads")
	uploadId := c.Query("uploadId")
	if uploadId != "" {
		return s.combineChunk(c)
	} else {
		return s.initChunk(c)
	}
}

// done
func (s *App) initChunk(c *fiber.Ctx) error {
	bucket := c.Params("bucket")
	object := c.Params("object")
	objectSuffix := c.Params("*")
	objectKey := path.Join(object, objectSuffix)
	uploadId, err := s.svc.InitiateMultipartUpload(bucket)
	if err != nil {
		return c.Status(http.StatusInternalServerError).XML(ErrorResponse{err.Error()})
	}

	multipartUploadResult := InitiateMultipartUploadResult{
		bucket,
		objectKey,
		uploadId,
	}
	return c.Status(http.StatusOK).XML(multipartUploadResult)
}

type CombineChunkRequest struct {
	Part PartETag `xml:"Part"`
}

// done
func (s *App) combineChunk(c *fiber.Ctx) error {
	bucket := c.Params("bucket")
	object := c.Params("object")
	objectSuffix := c.Params("*")
	objectKey := path.Join(object, objectSuffix)
	uploadId := c.Query("uploadId")
	var ccr CompleteMultipartUpload
	err := c.BodyParser(&ccr)
	if err != nil {
		return c.Status(http.StatusInternalServerError).XML(ErrorResponse{err.Error()})
	}
	eTag, err := s.svc.CompleteMultipartUpload(bucket, objectKey, uploadId, ccr.PartETags)
	if err != nil {
		return c.Status(http.StatusInternalServerError).XML(ErrorResponse{err.Error()})
	}
	return c.Status(http.StatusOK).XML(CompleteMultipartUploadResult{
		bucket,
		objectKey,
		eTag,
	})
}

func (s *App) deleteFile(c *fiber.Ctx) error {
	bucket := c.Params("bucket")
	object := c.Params("object")
	objectSuffix := c.Params("*")
	objectKey := path.Join(object, objectSuffix)
	err := s.svc.DeleteObject(bucket, objectKey)
	if err != nil {
		return c.Status(http.StatusInternalServerError).XML(ErrorResponse{err.Error()})
	}
	return c.Status(http.StatusOK).XML(nil)
}

func (s *App) deleteBucket(c *fiber.Ctx) error {
	bucket := c.Params("bucket")
	err := s.svc.DeleteBucket(bucket)
	if err != nil {
		return c.Status(http.StatusInternalServerError).XML(ErrorResponse{err.Error()})
	}

	return c.Status(http.StatusOK).XML(nil)
}

// done
func (s *App) getFile(c *fiber.Ctx) error {
	bucket := c.Params("bucket")
	object := c.Params("object")
	objectSuffix := c.Params("*")
	metaInfoFilePath := path.Join(s.dataDir, basicPathSuffix, bucket, object, objectSuffix) + ".meta"
	if !util.IsPathExist(metaInfoFilePath) {
		return c.Status(http.StatusNotFound).XML(nil)
	}
	metadata, err := fs.LoadMetadata(metaInfoFilePath)
	if err != nil {
		return c.Status(http.StatusInternalServerError).XML(nil)
	}

	var paths []string
	for _, chunk := range metadata.Chunks {
		chunkFilePath := fs.PathFromHash(chunk)
		paths = append(paths, chunkFilePath)
	}

	data, err := fs.MultiDecompressedReader(paths)
	if err != nil {
		return c.Status(http.StatusInternalServerError).XML(nil)
	}
	extraHeaders := map[string]string{
		"Last-Modified": metadata.Time.UTC().Format(http.TimeFormat),
		"Content-Disposition": fmt.Sprintf(`attachment; filename="%s"; filename*=UTF-8''%s`,
			url.QueryEscape(metadata.Name), url.QueryEscape(metadata.Name),
		),
		//"ETag": *resp.ETag,
	}

	c.Set("Content-Type", "application/octet‑stream")
	for k, v := range extraHeaders {
		c.Set(k, v)
	}
	return c.Status(http.StatusOK).SendStream(data, int(metadata.Size))
}

// done
func (s *App) getBucket(c *fiber.Ctx) error {
	bucket := c.Params("bucket")
	bucketPath := path.Join(s.dataDir, basicPathSuffix, bucket)
	files, _ := ioutil.ReadDir(bucketPath)

	var contents []Content
	for _, f := range files {
		if f.IsDir() {
			// TODO is dir
		} else if !f.IsDir() && strings.HasSuffix(f.Name(), ".meta") {
			metaFilePath := path.Join(bucketPath, f.Name())
			metadata, err := fs.LoadMetadata(metaFilePath)
			if err != nil {
				return c.SendStatus(http.StatusInternalServerError)
			}
			contents = append(contents, Content{
				Size:         metadata.Size,
				Key:          metadata.Name,
				LastModified: metadata.Time,
			})
		}
	}
	result := ListBucketResult{
		Name:        bucket,
		Prefix:      c.Query("prefix"),
		IsTruncated: false,
		MaxKeys:     100000,
		Contents:    contents,
	}
	// log.Printf("%+v", result)
	return c.Status(http.StatusOK).XML(result)
}

func (s *App) headBucket(c *fiber.Ctx) error {
	bucket := c.Params("bucket")
	bucketPath := path.Join(s.dataDir, basicPathSuffix, bucket)
	if util.IsDir(bucketPath) {
		return c.Status(http.StatusOK).XML("")
	} else {
		return c.Status(http.StatusNotFound).XML("")
	}
}

// done
func (s *App) listBucket(c *fiber.Ctx) error {
	dirPath := path.Join(s.dataDir, basicPathSuffix)
	if util.IsDir(dirPath) {
		var res []Bucket
		dir, _ := os.ReadDir(dirPath)
		for _, d := range dir {
			if d.IsDir() {
				info, _ := d.Info()
				modTime := info.ModTime()
				bucket := Bucket{
					Name:         d.Name(),
					CreationDate: util.DateFormatToSecond(modTime),
				}
				res = append(res, bucket)
			}
		}
		var bs []BucketWrapper
		for _, x := range res {
			var tmp BucketWrapper
			tmp.Bucket = x
			bs = append(bs, tmp)
		}
		return c.Status(http.StatusOK).XML(ListBucketResp{
			Id: "20230529",
			Owner: Owner{
				DisplayName: "minioadmin",
			},
			Buckets: bs,
		})
	} else {
		log.Println(dirPath, "is not a directory")
		return c.Status(http.StatusConflict).XML("")
	}
}

// done
func (s *App) headObject(c *fiber.Ctx) error {
	bucket := c.Params("bucket")
	object := c.Params("object")
	objectSuffix := c.Params("*")
	metaInfoFilePath := path.Join(s.dataDir, basicPathSuffix, bucket, object, objectSuffix) + ".meta"
	if !util.IsPathExist(metaInfoFilePath) {
		return c.Status(http.StatusNotFound).XML(map[string]string{
			"NoExist": "1",
		})
	}
	metadata, err := fs.LoadMetadata(metaInfoFilePath)
	if err != nil {
		return c.Status(http.StatusInternalServerError).XML(nil)
	}
	c.Set("Content-Disposition", "filename="+metadata.Name)
	c.Set("Content-Length", strconv.FormatInt(metadata.Size, 10))
	c.Set("Content-Type", metadata.Type)
	c.Set("Last-Modified", util.DateFormatToSecond(metadata.Time))
	return c.Status(http.StatusOK).XML(nil)
}
